/*
 * Copyright (c) 2009, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tssp_internal.h"
#include "tssp_table.h"
#include "tssp_dvb_eit.h"


struct tssp_dvb_eit_monitor {
    struct tssp_table_monitor *tmon;
    tssp_dvb_eit_monitor_callback callback;
    void *userdata;
};

struct tssp_dvb_eit *tssp_dvb_eit_parse(const struct tssp_section *section)
{
    int ret;
    int pos = 6;
    struct tssp_dvb_eit *eit;
    struct tssp_dvb_eit_event *eit_event;
    struct tssp_table_payload p;
    uint16_t loop_length;
    uint8_t *data;

    TSSP_ASSERT(section);

    if (!section->syntax_indicator)
        return NULL;

    /* prepare data buffer */
    p.length = section->data.end - section->data.start;
    p.start = section->data.start;
    p.end = section->data.end;
    p.heap = NULL;
    data = p.start;

    /* allocate eit */
    eit = tssp_dvb_eit_create();
    if (eit == NULL)
        goto cleanup;

    if (p.length < 6)
        goto cleanup;

    eit->service_id = section->extension;
    eit->version = section->version;
    eit->current_next = section->current_next;
    eit->ts_id = TSSP_TO_UINT16(data);
    eit->network_id = TSSP_TO_UINT16(&data[2]);
    eit->segment_last_section_number = data[4];
    eit->last_table_id = data[5];

    while (p.length >= (pos + 12)) {
        data = &p.start[pos];
        eit_event = tssp_dvb_eit_add_event(eit,
                                           TSSP_TO_UINT16(data),
                                           TSSP_TO_UINT40(&data[2]),
                                           TSSP_TO_UINT24(&data[7]),
                                           (0xE0 & data[10]) >> 5,
                                           (0x10 & data[10]) >> 4);
        if (eit_event == NULL)
            goto cleanup;

        loop_length = TSSP_TO_UINT12(&data[10]);
        pos += 12;

        /* descriptor loop */
        if (loop_length > 0) {
            if (p.length < pos)
                goto cleanup;

            ret = tssp_descriptor_parse(&eit_event->descriptors,
                    &data[12],
                    p.end,
                    loop_length);
            if (ret < 0)
                goto cleanup;
            pos += loop_length;
        }
    }
    if (p.length != pos)
        goto cleanup;

    tssp_table_free_payload(&p);
    return eit;

cleanup:
    tssp_dvb_eit_destroy(eit);
    tssp_table_free_payload(&p);
    return NULL;
}

struct tssp_dvb_eit *tssp_dvb_eit_create()
{
    struct tssp_dvb_eit *eit = malloc(sizeof(*eit));

    if (eit)
        STAILQ_INIT(&eit->events);

    return eit;
}

void tssp_dvb_eit_destroy(struct tssp_dvb_eit *eit)
{
    struct tssp_dvb_eit_event *tmp;

    if (eit) {
        while ((tmp = STAILQ_FIRST(&eit->events))) {
            STAILQ_REMOVE_HEAD(&eit->events, entry);
            tssp_dvb_eit_event_destroy(tmp);
        }
        free(eit);
    }
}

void tssp_dvb_eit_list_destroy(struct tssp_dvb_eit_list *eit_list)
{
    struct tssp_dvb_eit *tmp;

    if (eit_list) {
        while ((tmp = STAILQ_FIRST(eit_list))) {
            STAILQ_REMOVE_HEAD(eit_list, entry);
            tssp_dvb_eit_destroy(tmp);
        }
        free(eit_list);
    }
}

struct tssp_dvb_eit_event *
tssp_dvb_eit_add_event(struct tssp_dvb_eit *eit,
                       uint16_t event_id,
                       uint64_t start_time,
                       uint32_t duration,
                       uint8_t running_status,
                       uint8_t free_CA_mode)
{
    struct tssp_dvb_eit_event *eit_event;

    TSSP_ASSERT(eit);

    eit_event = malloc(sizeof(*eit_event));
    if (eit_event) {
        eit_event->event_id = event_id;
        eit_event->start_time = start_time;
        eit_event->duration = duration;
        eit_event->running_status = running_status;
        eit_event->free_CA_mode = free_CA_mode;
        STAILQ_INIT(&eit_event->descriptors);
        STAILQ_INSERT_TAIL(&eit->events, eit_event, entry);
    }

    return eit_event;
}

void tssp_dvb_eit_event_destroy(struct tssp_dvb_eit_event *eit_event)
{
    if (eit_event) {
        tssp_descriptors_destroy(&eit_event->descriptors);
        free(eit_event);
    }
}


static
void tssp_dvb_eit_monitor_section_callback(struct tssp_table_monitor *tmon,
                                           struct tssp_section *section,
                                           void *userdata)
{
    struct tssp_dvb_eit_monitor *mon = userdata;
    struct tssp_dvb_eit_list *eit_list;
    struct tssp_dvb_eit *eit;
    struct tssp_section *tmp;

    eit_list = malloc(sizeof(*eit_list));
    if (eit_list == NULL)
        goto cleanup;

    STAILQ_INIT(eit_list);

    tmp = section;
    while (tmp) {
        eit = tssp_dvb_eit_parse(tmp);
        if (eit)
            STAILQ_INSERT_TAIL(eit_list, eit, entry);
        else
            goto cleanup;
        tmp = tmp->next_section;
    }

    mon->callback(mon, eit_list, section, mon->userdata);
    return;

cleanup:
    tssp_section_destroy(section);
    tssp_dvb_eit_list_destroy(eit_list);
}

struct tssp_dvb_eit_monitor *
tssp_dvb_eit_monitor_attach(struct tssp *parser,
                            uint16_t service_id,
                            uint8_t table_id,
                            uint8_t current_next,
                            tssp_dvb_eit_monitor_callback callback,
                            void *userdata)
{
    struct tssp_dvb_eit_monitor *monitor;
    struct tssp_section_filter filter;

    if (parser == NULL || callback == NULL)
        return NULL;

    monitor = malloc(sizeof(*monitor));
    if (monitor == NULL)
        return NULL;

    tssp_section_filter_init(&filter, TSSP_PID_DVB_EIT, table_id,
                             service_id, 0xFFFF, current_next, 0);
    monitor->tmon = tssp_table_monitor_attach(parser,
                                              &filter,
                                              tssp_dvb_eit_monitor_section_callback,
                                              monitor);
    if (monitor->tmon == NULL) {
        free(monitor);
        return NULL;
    }

    monitor->callback = callback;
    monitor->userdata = userdata;

    return monitor;
}

void tssp_dvb_eit_monitor_detach(struct tssp_dvb_eit_monitor *monitor)
{
    if (monitor == NULL)
        return;

    TSSP_ASSERT(monitor->tmon);

    tssp_dvb_eit_monitor_reset(monitor);
    tssp_table_monitor_detach(monitor->tmon);
    free(monitor);
}

void tssp_dvb_eit_monitor_reset(struct tssp_dvb_eit_monitor *mon)
{
    TSSP_ASSERT(mon);
    tssp_table_monitor_reset(mon->tmon);
}

